package top.codedance.iotp.client.plugins.async.bacnet;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.client.ClientApplication;
import top.codedance.iotp.common.entity.CommonEntity;
import top.codedance.iotp.common.executer.AsyncAbstractExecuter;
import com.serotonin.bacnet4j.LocalDevice;
import com.serotonin.bacnet4j.RemoteDevice;
import com.serotonin.bacnet4j.event.DeviceEventAdapter;
import com.serotonin.bacnet4j.npdu.ip.IpNetwork;
import com.serotonin.bacnet4j.npdu.ip.IpNetworkBuilder;
import com.serotonin.bacnet4j.service.confirmed.SubscribeCOVRequest;
import com.serotonin.bacnet4j.transport.DefaultTransport;
import com.serotonin.bacnet4j.type.Encodable;
import com.serotonin.bacnet4j.type.EncodedValue;
import com.serotonin.bacnet4j.type.constructed.PropertyValue;
import com.serotonin.bacnet4j.type.constructed.SequenceOf;
import com.serotonin.bacnet4j.type.primitive.*;
import com.serotonin.bacnet4j.type.primitive.Boolean;
import com.serotonin.bacnet4j.util.RequestUtils;
import org.apache.log4j.Logger;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class BacNetIpService extends Service {
    private static Logger logger = ClientApplication.getLogger(BacNetIpService.class);
    private static int LoadDeviceId = 2001235;
    private LocalDevice localDevice;

    public BacNetIpService(AsyncAbstractExecuter executer, BacNetServiceParam param, int instanceId) {
        super(executer, param, instanceId);
    }

    @Override
    public void createConnect() {
        try{
            //创建网络对象
            IpNetwork ipNetwork = new IpNetworkBuilder()
                    //本机的ip
                    .withLocalBindAddress(param.getLocalBindAddress())
                    //掩码和长度，如果不知道本机的掩码和长度的话，可以使用代码的工具类IpNetworkUtils获取
                    .withSubnet(param.getSubnetAddress(), param.getNetworkPrefixLength())
                    //默认的UDP端口
                    .withPort(param.getPort())
                    .withReuseAddress(true)
                    .build();
            //创建虚拟的本地设备，随意设置一个deviceId 设备的唯一标识
            localDevice = new LocalDevice(LoadDeviceId++, new DefaultTransport(ipNetwork));
            //初始化本地设备
            localDevice.initialize();
            localDevice.getEventHandler().addListener(new DeviceEventAdapter() {
                @Override
                public void covNotificationReceived(final UnsignedInteger subscriberProcessIdentifier,
                                                    final ObjectIdentifier initiatingDevice, final ObjectIdentifier monitoredObjectIdentifier,
                                                    final UnsignedInteger timeRemaining, final SequenceOf<PropertyValue> listOfValues) {
                    PropertyValue propertyValue = listOfValues.get(0);
                    if (propertyValue.getPropertyArrayIndex() != null) {
                        try {
                            int deviceId = initiatingDevice.getInstanceNumber();
                            int property = propertyValue.getPropertyIdentifier().intValue();
                            int dataInstance = monitoredObjectIdentifier.getInstanceNumber();
                            int datatype = monitoredObjectIdentifier.getObjectType().intValue();
                            receipt(subscriberProcessIdentifier.intValue(), deviceId, property, datatype, dataInstance, propertyValue.getValue().toString());
                        } catch (Exception e){
                            logger.debug(e.getMessage());
                        }
                    }
                }
            });
            //搜寻网段内远程设备
            localDevice.startRemoteDeviceDiscovery();
        } catch (Exception e){
            logger.info("BACNet local device initialize fail.");
            logger.debug(e.getMessage());
        }
    }

    /**
     * 重启
     */
    private synchronized void restart(){
        localDevice.terminate();
        createConnect();
    }

    @Override
    public void subscribe(SubscribeParam subscribeParam) {
        try {
            RemoteDevice remoteDevice = localDevice.getRemoteDeviceBlocking(subscribeParam.getDeviceId());
            //发送订阅COV报文 对应为订阅标识（不可为0）,订阅对象,是否要发送确认报文,订阅时长(0为永久)
            localDevice.send(remoteDevice, new SubscribeCOVRequest(new UnsignedInteger(subscribeParam.getSubscriberProcess()), new ObjectIdentifier(subscribeParam.getDatatype(), subscribeParam.getDataInstance()), com.serotonin.bacnet4j.type.primitive.Boolean.TRUE, new UnsignedInteger(0))).get();
        }catch (Exception e){
            restart();
            logger.debug(e.getMessage());
        }
    }

    @Override
    public void unSubscribe(SubscribeParam subscribeParam) {
        try {
            RemoteDevice remoteDevice = localDevice.getRemoteDeviceBlocking(subscribeParam.getDeviceId());
            //订阅时长设为不为0的最小数，就是很快定阅过期删除。
            localDevice.send(remoteDevice, new SubscribeCOVRequest(new UnsignedInteger(subscribeParam.getSubscriberProcess()), new ObjectIdentifier(subscribeParam.getDatatype(), subscribeParam.getDataInstance()), com.serotonin.bacnet4j.type.primitive.Boolean.TRUE, new UnsignedInteger(1))).get();
        }catch (Exception e){
            restart();
            logger.debug(e.getMessage());
        }
    }


    @Override
    public void close() {
        localDevice.terminate();
    }

    @Override
    public boolean isRunning() {
        return localDevice.isInitialized();
    }

    @Override
    public void receipt(int subscriberProcess, int deviceId, int property, int datatype, int dataInstance, Object body) {
        synchronized (BacnetExecuterImpl.class) {
            Set<Map.Entry<Integer, Set<SubscribeParam>>> entries = BacnetExecuterImpl.tasks.entrySet();
            Iterator<SubscribeParam> iterator;
            SubscribeParam item;
            for (Map.Entry<Integer, Set<SubscribeParam>> entry : entries){
                if(entry.getKey() == super.instanceId) {
                    iterator = entry.getValue().iterator();
                    while (iterator.hasNext()) {
                        item = iterator.next();
                        if (item.getSubscriberProcess() == subscriberProcess || (item.getDeviceId() == deviceId && item.getProperty() == property && item.getDatatype() == datatype && item.getDataInstance() == dataInstance)) {
                            JSONObject jo = new JSONObject();
                            jo.put(item.getTag(), body);
                            CommonEntity ce = genCommonEntity();
                            ce.setBody(jo.toJSONString().getBytes(StandardCharsets.UTF_8));
                            try {
                                executer.send(ce.build(), item.getClientId());
                                logger.debug("Send data to " + item.getClientId());
                            } catch (Exception e) {
                                logger.debug(e.getMessage());
                            }
                        }
                    }
                }
            }
        }
    }

    @Override
    public void send(JSONObject item) {
        try {
            //获取某个远程设备
            RemoteDevice remoteDevice = localDevice.getRemoteDeviceBlocking(item.getIntValue("deviceId"));
            Encodable value;
            Object v = item.get("value");
            if(v instanceof java.lang.Boolean) {
                value = item.getBooleanValue("value") ? Boolean.TRUE : Boolean.FALSE;
            }else if(v instanceof BigDecimal){
                value = new Real(item.getFloatValue("value"));
            } else if(v instanceof Integer) {
                value = new SignedInteger(item.getIntValue("value"));
            } else {
                value = new EncodedValue(item.getBytes("value"));
            }
            RequestUtils.writePresentValue(localDevice, remoteDevice, new ObjectIdentifier(item.getIntValue("datatype"), item.getIntValue("instance")), value);
        } catch (Exception e){
            logger.debug(e.getMessage());
        }
    }

    @Override
    public void send(JSONArray writeArray) {
        for (int i = 0;i<writeArray.size();i++){
            send(writeArray.getJSONObject(i));
        }
    }
}
